5

Maven生命周期与插件

除了坐标、依赖以及仓库之外,Maven另外两个核心概念是生命周期和插件。命令行的输入往往对应生命周期,而生命周期是抽象的,实际行为由插件完成。

何为生命周期

在Maven出现之前,项目构建的生命周期就已经存在,清理、编译、测试及部署。但公司与公司间,项目与项目间,往往使用不同方式做类似的工作。
Maven的生命周期对所有的构建过程进行抽象和统一。这个生命周期包括项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。
Maven的生命周期是抽象的,实际任务都交由插件来完成。这种思想与设计模式中的模板方法(Template Method)非常相似,生命周期是模板方法,而插件就是子类覆写的方法。

总结:Maven的生命周期定义了项目构建的流程,而插件负责实现流程中每个步骤,最后将插件与生命周期流程中每个步骤绑定即可完成项目构建。在这个过程中插件可以在多个项目中复用,而通过更换插件绑定亦可实现差异化构建。

生命周期详解

Maven拥有三套相互独立的生命周期。

生命周期 阶段(phase) 说明
clean pre-clean 执行清理前需要完成的工作
clean 清理上一次构建生成的文件
post-clean 执行清理后需要完成的工作
default vaildate 验证,确保当前配置和POM内容是有效的,包含对POM文件树的验证。
intianlize 初始化,执行构建生命周期的主任务之前的初始化
generate-sources 生成源码,代码生成器生成后期阶段中处理或编译的源代码
proccess-sources 处理源码,提供解析、修改和转换源码。常规源码和生成的源码都可以再这里处理
generate-resoureces 生成资源,生成非源码资源,通常包括元数据文件和配置文件
process-resources 处理资源,处理非源码资源,修改、转换和重定位资源都能在这阶段发生
compile 编译,编译源码。编译过的类被放到目标目录树中
process-classes 处理类,处理类文件转换和增强步骤。字节码交织器和常用工具常在这一阶段操作
generate-test-sources 生成测试源码,生成要操作的单元测试代码
process-test-sources 处理测试源码,在编译前对测试源码执行任何必要的处理。修改、转换或复制源代码
generate-test-resources 生成测试资源,生成与测试相关的非源码资源
process-test-resources 处理测试资源,处理、转换或重新定位于测试相关的资源
test-compile 测试编译,编译单元测试的源码
process-test-classes 处理测试类,对编译生成文件做后期处理(Maven2.0.5及以上)
test 测试,运行编译过的单元测试并累计结果
prepare-package 执行打包前的所有操作(Maven2.1及以上)
package 打包,将可执行的二进制文件打包到一个分布式归档文件中,如jar或war
pre-integration-test 前集成测试,准备集成测试,将归档文件部署到一个服务器上执行
integration-test 集成测试,执行真正的集成测试,指在一个受到一定控制的模拟的真实部署环境中测试代码
post-integration-test 后集成测试,解除集成测试准备,涉及环境重置或重新初始化
verify 检验,检验可部署归档文件的有效性和完整性,通过后,将安装该归档
install 安装,将项目包安装到本地仓库,供其他项目依赖
deploy 部署,将项目发布到远程仓库,供其他开发人员与项目共享
site pre-site 执行一些在生成项目站点之前需要完成的工作
site 生成项目站点文档
peo-site 执行一些在生成项目站点之后需要完成的工作
site-deploy 将生成的项目站点发布到服务器上

生命周期详解

从命令行执行Maven任务的最主要方式就是调用Maven的生命周期阶段。
Ps:Maven的三套生命周期是相互独立的,而一个生命周期阶段是有前后依赖关系的。
如:
mvn clean:该命令调用clean生命周期的clean阶段,实际执行clean生命周期中的pre-clean,clean阶段
mvn test:该命令调用default生命周期的test阶段,实际执行default生命周期中vaildate至test所有阶段
mvn clean install:该命令调用clean生命周期的clean阶段以及default生命周期的install阶段,实际执行clean生命周期中的pre-clean,clean阶段,以及default生命周期的vaildate至install所有阶段。
mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段、default生命周期的deploy阶段以及site生命周期的site-deploy阶段,实际执行实际执行clean生命周期中的pre-clean,clean阶段、default生命周期的所有阶段以及site生命周期的所有阶段。
由于Maven中主要生命周期阶段不多,而常用Maven命令都是基于这些阶段简单组合而成,因此只要对Maven生命周期有基本的理解,就可以正确而熟练地使用Maven命令

插件目标

Maven的核心仅定义了抽象的生命周期,具体的任务是交由插件完成,插件以独立的构件形式存在。
对于插件本身,为了代码复用,它往往具备多个功能,而每个功能都统称为插件目标(Plugin Goal)。
如:maven-dependency-plugin,基于项目依赖做很多事情。
1 帮助分析项目依赖,帮助找出潜在的无用依赖;
2 列出项目依赖树,帮助分析依赖来源
3 列出项目已解析的依赖 等...
这些任务有很多代码可以复用。因此,这些功能聚集在一个插件中,每个功能就是一个插件目标。
插件目标使用语法:
1 完整命令 mvn groupId:artifactId:version:goal 如 mvn org.apache.maven.plugins:maven-dependency-plugin:3.0.2:tree
2 简化version mvn groupId:artifactId:goal 如 mvn org.apache.maven.plugins:maven-dependency-plugin:tree
3 使用插件前缀 mvn 插件前缀:goal 如 mvn dependency:tree

插件绑定

Maven的生命周期与插件目标相互绑定,用以完成实际的构建任务。

内置绑定

为了使用户几乎不用任何配置就能构建Maven项目,Maven在核心为一些主要的生命周期阶段绑定了很多插件的目标,
当用户通过命令行调用生命周期时,对应的插件目标就会执行相应的任务。
如下图(default生命周期的阶段与插件目标的绑定关系由项目打包类型决定(packaging元素)下图以jar包构建为例

生命周期 阶段(phase) 内置插件 执行任务
clean pre-clean
clean maven-clean-plugin:clean 删除项目的输出目录
post-clean
default vaildate
intianlize
generate-sources
proccess-sources
generate-resoureces
process-resources maven-resources-plugin:resources 复制主资源文件至主输出目录
compile maven-compiler-plugin:compile 编译主代码至主输出目录
process-classes
generate-test-sources
process-test-sources
generate-test-resources
process-test-resources maven-resources-plugin:testResources 复制测试资源文件至测试输出目录
test-compile maven-compiler-plugin:testCompile 编译测试代码至测试输出目录
process-test-classes
test maven-surefire-plugin:test 执行测试用例
prepare-package
package maven-jar-plugin:jar 创建项目jar包
pre-integration-test
integration-test
post-integration-test
verify
install maven-install-plugin:install 将项目输出构件安装到本地仓库
deploy maven-deploy-plugin:deploy 将项目输出构件安装到远程仓库
site pre-site
site maven-site-plugin:site 生成项目站点
peo-site
site-deploy maven-site-plugin:deploy 将项目站点部署到远程服务器上

Ps:空白的生命周期阶段,默认没有绑定任何插件,因此也没有任何实际行为
除默认打包类型jar外,常见的打包类型还有war、pom、maven-plugin、ear等。查看相关类型插件绑定官网http://maven.apache.org/guide...

自定义绑定

除了内置绑定以外,用户可以选择将某个插件目标绑定到生命周期的某个阶段,能让Maven项目在构建过程中执行更多更富特色的任务。
如maven-source-plugin:jar-no-fork,能够将项目主代码打成jar文件。将其绑定到default生命周期的verify阶段上,在执行完继承测试后和安装构件之前创建源码jar包。配置如下

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>3.0.1</version>
            <executions>    <!-- 插件执行配置 -->
                <execution>    <!-- executions下每个execution子元素可以配置执行一个任务 -->
                    <id>attach-sources</id>    <!-- 配置任务id -->
               <!-- <phase>verify</phase> -->  <!-- phase元素配置绑定生命周期阶段 -->
                    <goals>    <!-- 配置要执行的插件目标 -->
                        <goal>jar-no-fork</goal>
                    </goals>
                <execution>
            </executions>
        </plugin>
    </plugins>
</build>

完成自定义插件绑定后,运行mvn verify即可。
在上述代码中,注释了phase元素也能实现绑定,原因是很多插件目标在编写时已经定义了默认绑定阶段,可以使用maven-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。
mvn help:describe -Dplugin=groupId:artifactId:version

当插件目标绑定到不同的生命周期时,其执行顺序会由生命周期阶段的先后顺序决定。如果多个目标被绑定到同一个阶段,这些插件声明的先后顺序决定目标的执行顺序。

插件配置

完成插件目标与生命周期绑定后,用户还可以配置插件目标的参数,进一步调整插件目标所执行的任务,以满足项目的需求。几乎所有Maven插件目标都有一些可配置参数,可通过命令行和POM配置等方式来配置参数。

1)命令行插件配置

使用-D参数,并伴随一个参数名=参数值的形式,来配置参数
(命令行参数是由插件参数的表达式(Expression)决定,并非所有插件目标参数都有表达式,只能在POM中配置)
如maven-surrfire-plugin插件提供了一个maven.test.skip参数,当其值为true时,就会跳过执行测试。
mvn install -Dmaven.test.skip=true

2)POM中插件全局配置

并非所有插件参数都适合从命令行配置,有些参数的值从项目构建到项目发布都不会改变,或很少改变,在POM文件中一次性配置显然比重复在命令行输入要方便。
如mvan-compiler-plugin 可以配置全局参数 实现compile以及testCompile任务都能使用全局配置。

<build>
    <plugins>
        <plugin>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <configuration>    <!-- 声明插件全局配置 所有基于该插件目标的任务,都会使用这些配置 -->
                ...
            </configuration>
        </plugin>
    </plugins>
</builds>

3)POM中插件任务配置

除了为插件配置全局的参数,还可以为某个插件任务配置特定的参数。
如maven-antrun-plugin 可以配置插件任务参数,使run目标任务在不同生命周期输出不同的语句。

<build>
    <plugins>
        <plugin>
            <groupId>...</groupId>
            <artifactId>...</artifactId>
            <version>...</version>
            <executions>
                <execution>
                    <id>...</id>
                    <phase>...</phase>
                    <goals>
                        <goal>...</goal>
                    </goals>
                    <configuration>    <!-- 插件任务一配置 -->
                        ...
                    </configuration>
                </execution>
                <execution>
                    <id>...</id>
                    <phase>...</phase>
                    <goals>
                        <goal>...</goal>
                    </goals>
                    <configuration>    <!-- 插件任务二配置 -->
                        ...
                    </configuration>
                </execution>
                ...
            </executions>
        </plugin>
    </plugins>
</builds>

获取插件信息

仅仅理解如何配置使用插件是不够的,实现一个构建任务,用户需知道去哪找到合适的插件,并详细了解该插件的配置点。由于Maven的插件非常多,而且这其中大部分没有完善的文档,因此使用正确的插件并进行正确的配置,不容易。

1)在线插件信息

基本上所有的Maven插件都来自于apache和Codehaus。

apache 说明 官方插件,用户多,稳定性好
详细列表 http://maven.apache.org/plugi...
下载地址 http://repo1.maven.org/maven2...
Codehaus 说明 文档和可靠性相对较差,遇到问题,往往需要自己看源码
详细列表 http://mojo.codehaus.org/plug...
下载地址 http://repository.codehaus.or...

虽然并非所有插件都提供了完善的文档,但一些核心插件的文档还是非常丰富的。
一般来说,通过阅读插件在文档中的使用介绍和实例,就应该能够在自己的项目中很好地使用该插件。
当我们需要了解非常细节的目标参数时,就需要进一步访问该插件每个目标的文档。文档详细解释了该参数的作用、类型等信息。

2)使用maven-help-plugin插件

除了访问在线的插件文档之外,还可以借助maven-help-plugin来获取插件的详细信息。
执行maven-help-plugin的describe目标,指定要查询的插件的坐标,可查询插件的坐标,前缀(Goal Prefix),目标信息
mvn help:describe -Dplugin = (groupId:artifactId[:version] | Goal Prefix) -Dgoal = goal -Ddetail

从命令行调用插件

mvn -h 显示mvn命令帮助,可以看到如下信息:

usage:mvn [options] [<goal(s)]>] [<phase(s)>]
Options:
...

options表示可用的选项,除了选项之外,mvn命令后面可以添加一个到多个goal和phase,分别指插件目标和生命周期阶段。mvn命令可以激活生命周期阶段,从而执行那些绑定在生命周期阶段上的插件目标。也可以直接执行插件目标,因为有些插件目标不适用于生命周期阶段,如maven-help-plugin:describe。
直接执行插件目标语法 在上文中已提及,可在插件目标中查看。

插件解析机制

在命令行中执行插件目标,可使用插件前缀替代坐标,方便用户使用和配置插件。
Maven的这一特性是双刃剑,虽然它简化了插件的使用和配置,但如果出现问题,用户很难定位出问题的插件构件。
如mvn help:system 执行了什么插件。它的坐标是什么。这与Maven的插件解析机制有关。

1)插件仓库

与依赖构件一样,插件构件同样基于坐标存储在Maven仓库中,在需要的时候,Maven会先从本地仓库寻找插件,不存在则从远程仓库找到插件,下载到本地仓库使用。
插件的远程仓库不同于依赖的远程仓库,配置方式也不同,插件仓库配置如下:

<pluginRepositories>
    <pluginRepository>
        ...    <!-- 此处与依赖远程仓库配置一样,可参考阅读总结二查看 -->
    </pluginRepository>
    ...
</pluginRepositoties>

Maven内置了插件仓库指向中央仓库,并关闭了对SNAPSHOT的支持。
一般来说,中央仓库所包含的插件完全能够满足我们的需要,因此也不需要配置其他的插件仓库,只有在很少的情况下,项目使用的插件无法在中央仓库找到,或者自己编写了插件,可以参考上述配置,在POM中或settings.xml中 加入其他插件仓库。

2)插件的默认groupId

在POM中配置插件时,如果该插件是Maven官方插件(groupId为org.apache.maven.plugins),则可以省略groupId配置。
不推荐使用这一特性,只省略一行配置,但会让团队中不熟悉Maven的成员感到费解。

3)解析插件版本

同样为了简化插件的配置和使用,用户可没有提供插件的版本,Maven会自动解析插件版本。
首先,Maven在超级POM中为所有核心插件设定了版本,超级POM是所有Maven项目的父POM,所有项目都继承了这个超级POM配置。所以用户使用插件未设定插件版本的情况有以下几种:

1 核心插件:通过超级POM继承设定版本
2 非核心插件:通过仓库元数据 groupId/artifactId/maven-metadata.xml ,遍历并归并本地仓库和远程仓库的仓库元数据,根据latest和release计算出插件的版本。Maven3之后使用release,避免使用latest获取到快照版本,因为快照版本的频繁更新会导致插件行为的不稳定。

4)解析插件前缀

mvn命令行支持使用插件前缀来简化插件的调用。Maven是如何通过插件前缀获取插件的坐标的?
插件前缀与groupId:artifactId是一一对应的,这种匹配关系存储在仓库元数据中,该仓库员数据位于groupId/maven-metadate.xml,Maven在解析插件仓库元数据时,会默认使用org.apache.maven.plugins和org.codehaus.mojo两个groupId,可以再settings.xml中配置其他groupId

<settings>
    ...
    <pluginGroups>
        <pluginGroup>...</pluginGroup>
    </pluginGroups>
    ...
<settings>

插件仓库元数据中存储了所有插件前缀与group:artifactId的对应关系
插件仓库元数据检查顺序为:apache -> codehaus -> 用户自定义插件组 -> 都不包含该前缀,则报错


roylion
204 声望25 粉丝

读书破万卷